package netstack

import (
    "go-os-qemu/pkg/vga"
    "go-os-qemu/pkg/nic"
)

// Basic constants
const (
    EtherTypeARP  = 0x0806
    EtherTypeIPv4 = 0x0800
    ProtoICMP     = 1
    ProtoTCP      = 6
    ProtoUDP      = 17
)

// Local node configuration (default for QEMU user-net)
var MyIP = [4]byte{10, 0, 2, 15}
var MyMAC [6]byte
// Routing config
var Netmask = [4]byte{255, 255, 255, 0}
var Gateway = [4]byte{10, 0, 2, 2}
var DNS = [4]byte{10, 0, 2, 3}

// ARP cache (tiny, fixed size)
type arpEntry struct {
    ip   [4]byte
    mac  [6]byte
    valid bool
}

var arpCache [8]arpEntry

func setCache(ip [4]byte, mac [6]byte) {
    // Simple replacement policy: first invalid or overwrite same IP
    for i := 0; i < len(arpCache); i++ {
        if arpCache[i].valid && arpCache[i].ip == ip {
            arpCache[i].mac = mac
            return
        }
    }
    for i := 0; i < len(arpCache); i++ {
        if !arpCache[i].valid {
            arpCache[i] = arpEntry{ip: ip, mac: mac, valid: true}
            return
        }
    }
    // fallback: overwrite index 0
    arpCache[0] = arpEntry{ip: ip, mac: mac, valid: true}
}

// LookupMAC returns MAC for given IP if cached.
func LookupMAC(ip [4]byte) (mac [6]byte, ok bool) {
    for i := 0; i < len(arpCache); i++ {
        if arpCache[i].valid && arpCache[i].ip == ip {
            return arpCache[i].mac, true
        }
    }
    return [6]byte{}, false
}

// Helpers
func printDec(n int) {
    if n == 0 { vga.Print("0"); return }
    var buf [16]byte
    i := len(buf)
    for n > 0 && i > 0 {
        i--
        buf[i] = byte('0' + (n % 10))
        n /= 10
    }
    for ; i < len(buf); i++ {
        vga.Print(string(rune(buf[i])))
    }
}

func printHexNibble(n uint8) {
    d := n & 0x0F
    if d < 10 { vga.Print(string(rune('0' + d))) } else { vga.Print(string(rune('A' + (d - 10)))) }
}
func printHexByte(b byte) { printHexNibble(uint8(b>>4)); printHexNibble(uint8(b&0x0F)) }

func printIP(ip [4]byte) {
    for i := 0; i < 4; i++ {
        if i != 0 { vga.Print(".") }
        printDec(int(ip[i]))
    }
}

// HandleEther parses minimal Ethernet frames for ARP and ICMP replies.
func HandleEther(b []byte) {
    if len(b) < 14 { return }
    etherType := uint16(b[12])<<8 | uint16(b[13])
    if etherType == EtherTypeARP {
        handleARP(b[14:])
    } else if etherType == EtherTypeIPv4 {
        handleIPv4(b[14:])
    }
}

// ARP
func handleARP(p []byte) {
    if len(p) < 28 { return }
    // htype(2), ptype(2), hlen(1), plen(1), oper(2)
    htype := uint16(p[0])<<8 | uint16(p[1])
    ptype := uint16(p[2])<<8 | uint16(p[3])
    hlen := p[4]
    plen := p[5]
    oper := uint16(p[6])<<8 | uint16(p[7])
    if htype != 1 || ptype != EtherTypeIPv4 || hlen != 6 || plen != 4 { return }
    if len(p) < 8+6+4+6+4 { return }
    sha := [6]byte{p[8], p[9], p[10], p[11], p[12], p[13]}
    spa := [4]byte{p[14], p[15], p[16], p[17]}
    tha := [6]byte{p[18], p[19], p[20], p[21], p[22], p[23]}
    _ = tha
    tpa := [4]byte{p[24], p[25], p[26], p[27]}
    if oper == 2 { // reply
        // Cache sender's IP->MAC
        setCache(spa, sha)
        vga.SetColor(vga.ColorLightGreen, vga.ColorBlack)
        vga.Print("ARP reply ")
        printIP(spa)
        vga.Print(" MAC ")
        for i := 0; i < 6; i++ { if i != 0 { vga.Print(":") }; printHexByte(sha[i]) }
        vga.Println("")
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
    } else if oper == 1 { // request
        // If the request targets our IP, send ARP reply
        if tpa == MyIP {
            vga.SetColor(vga.ColorYellow, vga.ColorBlack)
            vga.Print("ARP who-has ")
            printIP(tpa)
            vga.Print("? from ")
            printIP(spa)
            vga.Println("")
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            // Attempt to send reply using NIC
            if e, ok := nic.FindE1000(); ok {
                // Ensure our MAC known
                ensureMAC(e)
                var frame [60]byte
                // Ethernet header: dst = requester MAC, src = our MAC, type ARP
                for i := 0; i < 6; i++ { frame[i] = sha[i] }
                for i := 0; i < 6; i++ { frame[6+i] = MyMAC[i] }
                frame[12] = 0x08; frame[13] = 0x06
                // ARP payload reply
                off := 14
                frame[off+0] = 0x00; frame[off+1] = 0x01               // HTYPE Ethernet
                frame[off+2] = 0x08; frame[off+3] = 0x00               // PTYPE IPv4
                frame[off+4] = 0x06                                    // HLEN
                frame[off+5] = 0x04                                    // PLEN
                frame[off+6] = 0x00; frame[off+7] = 0x02               // OPER reply
                // SHA (our MAC)
                for i := 0; i < 6; i++ { frame[off+8+i] = MyMAC[i] }
                // SPA (our IP)
                for i := 0; i < 4; i++ { frame[off+14+i] = MyIP[i] }
                // THA (requester MAC)
                for i := 0; i < 6; i++ { frame[off+18+i] = sha[i] }
                // TPA (requester IP)
                for i := 0; i < 4; i++ { frame[off+24+i] = spa[i] }
                e.SendFrame(frame[:])
            }
        }
    }
}

// IPv4 + ICMP minimal parsing
func handleIPv4(p []byte) {
    if len(p) < 20 { return }
    ihl := int(p[0] & 0x0F)
    if ihl < 5 || len(p) < ihl*4 { return }
    total := int(p[2])<<8 | int(p[3])
    proto := p[9]
    src := [4]byte{p[12], p[13], p[14], p[15]}
    // dst := [4]byte{p[16], p[17], p[18], p[19]}
    if proto == ProtoICMP && len(p) >= ihl*4+8 { // ICMP
        icmp := p[ihl*4:]
        typ := icmp[0]
        code := icmp[1]
        if typ == 0 && code == 0 { // echo reply
            vga.SetColor(vga.ColorLightGreen, vga.ColorBlack)
            vga.Print("ICMP echo reply from ")
            printIP(src)
            vga.Print(" len=")
            printDec(total)
            vga.Println("")
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        } else if typ == 3 { // Destination Unreachable
            vga.SetColor(vga.ColorYellow, vga.ColorBlack)
            vga.Print("ICMP dest unreachable from ")
            printIP(src)
            vga.Print(" code=")
            printDec(int(code))
            vga.Println("")
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        }
    } else if proto == ProtoUDP && len(p) >= ihl*4+8 {
        udp := p[ihl*4:]
        sport := int(udp[0])<<8 | int(udp[1])
        dport := int(udp[2])<<8 | int(udp[3])
        ulen := int(udp[4])<<8 | int(udp[5])
        if ulen >= 8 && len(udp) >= ulen {
            payload := udp[8:ulen]
            if sport == 53 { // DNS response
                handleDNSResponse(payload, src)
            } else if dispatchUDP(uint16(dport), src, uint16(sport), payload) {
                // delivered to listener
            } else {
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                vga.Print("UDP from ")
                printIP(src)
                vga.Print(":")
                printDec(sport)
                vga.Print(" -> ")
                printDec(dport)
                vga.Print(" len=")
                printDec(len(payload))
                vga.Println("")
            }
        }
    } else if proto == ProtoTCP && len(p) >= ihl*4+20 {
        tcp := p[ihl*4:]
        doff := int((tcp[12] >> 4) & 0x0F); if doff < 5 { doff = 5 }
        flags := tcp[13]
        sport := uint16(int(tcp[0])<<8 | int(tcp[1]))
        dport := uint16(int(tcp[2])<<8 | int(tcp[3]))
        // seq/ack numbers
        seq := uint32(tcp[4])<<24 | uint32(tcp[5])<<16 | uint32(tcp[6])<<8 | uint32(tcp[7])
        ack := uint32(tcp[8])<<24 | uint32(tcp[9])<<16 | uint32(tcp[10])<<8 | uint32(tcp[11])
        win := uint16(int(tcp[14])<<8 | int(tcp[15]))
        // payload length
        plen := total - ihl*4 - doff*4
        if plen < 0 { plen = 0 }
        // find matching connection by local port
        var c *TCPConn
        for i := 0; i < tcpCount; i++ {
            if tcpConns[i].srcPort == dport && tcpConns[i].dstPort == sport && tcpConns[i].dstIP == src {
                c = &tcpConns[i]
                break
            }
        }
        if c == nil {
            // Not a managed connection; minimal log for SYN+ACK
            if (flags & 0x12) == 0x12 {
                vga.SetColor(vga.ColorLightGreen, vga.ColorBlack)
                vga.Print("TCP SYN-ACK from "); printIP(src); vga.Print(":"); printDec(int(sport)); vga.Print(" -> "); printDec(int(dport)); vga.Println("")
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            }
            return
        }
        // Update peer window
        c.window = win
        if (flags & 0x12) == 0x12 && c.state == TCP_SYN_SENT { // SYN+ACK
            // advance expected from peer
            c.ack = seq + 1
            c.state = TCP_ESTABLISHED
            // Send pure ACK
            if e, ok := nic.FindE1000(); ok {
                tcpSendSegment(e, c, 0x10, nil)
                // try send any queued data
                tcpTrySend(e, c)
            }
            vga.SetColor(vga.ColorLightGreen, vga.ColorBlack)
            vga.Print("TCP established with "); printIP(src); vga.Print(":"); printDec(int(sport)); vga.Println("")
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        } else {
            // ACK processing for our outstanding data
            if (flags & 0x10) != 0 { // ACK flag
                // If peer ACKed up to our seq, clear outstanding
                if ack >= c.seq {
                    // compute acked bytes
                    acked := int(ack - c.unackedSeq)
                    if acked < 0 { acked = 0 }
                    c.lastLen = 0
                    c.outLen = 0
                    c.timer = 0
                    // congestion control: slow start / additive
                    if c.cwnd < c.ssthresh {
                        c.cwnd += c.mss
                    } else {
                        // additive increase by MSS
                        c.cwnd += c.mss
                    }
                    // advance send queue head if data was acknowledged
                    for acked > 0 && c.qHead != c.qTail {
                        n := c.qLen[c.qHead]
                        if acked >= n {
                            // drop whole chunk
                            c.qLen[c.qHead] = 0
                            c.qHead = (c.qHead + 1) % len(c.qLen)
                            acked -= n
                        } else {
                            // drop leading part; shift remaining within buffer
                            // simplistic: treat as fully acknowledged
                            c.qLen[c.qHead] = 0
                            c.qHead = (c.qHead + 1) % len(c.qLen)
                            acked = 0
                        }
                    }
                    // send next chunk if available
                    if e, ok := nic.FindE1000(); ok {
                        tcpTrySend(e, c)
                    }
                }
            }
            // data from peer
            if plen > 0 && len(tcp) >= doff*4+plen {
                payload := tcp[doff*4 : doff*4+plen]
                // advance expected
                c.ack = seq + uint32(plen)
                // echo ACK
                if e, ok := nic.FindE1000(); ok {
                    tcpSendSegment(e, c, 0x10, nil)
                }
                // print first N bytes
                vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                vga.Print("TCP data "); printDec(plen); vga.Print(" bytes from "); printIP(src); vga.Print(":"); printDec(int(sport)); vga.Println("")
                max := 128; if plen < max { max = plen }
                for i := 0; i < max; i++ {
                    b := payload[i]
                    if b >= 32 && b < 127 { vga.Print(string(rune(b))) } else { vga.Print(".") }
                }
                vga.Println("")
            }
            // FIN handling
            if (flags & 0x01) != 0 { // FIN
                // peer requests close
                c.ack = seq + 1
                if e, ok := nic.FindE1000(); ok { tcpSendSegment(e, c, 0x10, nil) } // ACK FIN
                if c.state == TCP_ESTABLISHED {
                    c.state = TCP_CLOSE_WAIT
                    // active close our side
                    if e, ok := nic.FindE1000(); ok { tcpSendSegment(e, c, 0x11, nil) } // FIN+ACK
                    c.state = TCP_LAST_ACK
                    c.timer = 0
                } else if c.state == TCP_FIN_WAIT2 {
                    c.state = TCP_TIME_WAIT
                    c.timer = 0
                }
            }
            // If ACK acknowledges our FIN
            if (flags & 0x10) != 0 {
                if c.state == TCP_FIN_WAIT1 {
                    // our FIN consumes 1 seq
                    if ack >= c.seq { c.state = TCP_FIN_WAIT2 }
                } else if c.state == TCP_LAST_ACK {
                    // close complete
                    c.state = TCP_CLOSED
                    c.used = false
                }
            }
        }
    }
}

// UDP listener registry
type UDPCallback func(srcIP [4]byte, srcPort uint16, data []byte)
var udpPorts [16]uint16
var udpCbs [16]UDPCallback
var udpCount int

// ListenUDP registers a port with a callback
func ListenUDP(port uint16, cb UDPCallback) bool {
    // check existing
    for i := 0; i < udpCount; i++ {
        if udpPorts[i] == port { udpCbs[i] = cb; return true }
    }
    if udpCount < len(udpPorts) {
        udpPorts[udpCount] = port
        udpCbs[udpCount] = cb
        udpCount++
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        vga.Print("UDP listen ")
        printDec(int(port))
        vga.Println("")
        return true
    }
    vga.SetColor(vga.ColorYellow, vga.ColorBlack)
    vga.Println("UDP listen table full")
    vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
    return false
}

// ---------------- TCP: minimal client with SYN->ESTABLISHED, ACK processing, retransmission ----------------
const (
    TCP_CLOSED      = 0
    TCP_SYN_SENT    = 1
    TCP_ESTABLISHED = 2
    TCP_FIN_WAIT1   = 3
    TCP_FIN_WAIT2   = 4
    TCP_TIME_WAIT   = 5
    TCP_CLOSE_WAIT  = 6
    TCP_LAST_ACK    = 7
)

type TCPConn struct {
    dstIP   [4]byte
    dstPort uint16
    srcPort uint16
    seq     uint32 // next seq to send
    ack     uint32 // next expected seq from peer
    unackedSeq uint32 // seq of last outstanding (SYN/data)
    window  uint16 // peer's advertised window (snd.wnd)
    cwnd    uint16 // congestion window (bytes)
    ssthresh uint16
    mss     uint16
    state   uint8
    retries int
    timer   uint32  // general timer (ticks)
    rto     uint32  // retransmission timeout (ticks)
    backoff uint8   // exponential backoff exponent
    lastLen int // last outstanding payload length (0 if none)
    lastData [256]byte
    // very small send queue
    qBuf   [4][512]byte
    qLen   [4]int
    qHead  int
    qTail  int
    outLen int // currently outstanding bytes (from unackedSeq)
    used   bool
}

var tcpConns [2]TCPConn
var tcpCount int

func newTCPConn(dstIP [4]byte, dstPort uint16) *TCPConn {
    if tcpCount >= len(tcpConns) { return nil }
    c := &tcpConns[tcpCount]
    *c = TCPConn{dstIP: dstIP, dstPort: dstPort, srcPort: uint16(40001 + tcpCount), seq: 1, ack: 0, window: 1024, cwnd: 536, ssthresh: 4096, mss: 536, state: TCP_CLOSED, rto: 100, used: true}
    tcpCount++
    return c
}

func tcpSendSegment(e *nic.E1000, c *TCPConn, flags byte, payload []byte) bool {
    ensureMAC(e)
    nh := nextHop(c.dstIP)
    mac, ok := LookupMAC(nh)
    if !ok {
        mac, ok = ResolveMACBlocking(e, nh, 8)
        if !ok {
            vga.SetColor(vga.ColorYellow, vga.ColorBlack)
            vga.Println("TCP: MAC unknown; ARP unresolved")
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            return false
        }
    }
    var buf [1024]byte
    // Ethernet
    for i := 0; i < 6; i++ { buf[i] = mac[i] }
    for i := 0; i < 6; i++ { buf[6+i] = MyMAC[i] }
    buf[12] = 0x08; buf[13] = 0x00
    // IPv4
    ip := buf[14:34]
    ip[0] = 0x45
    ip[1] = 0
    ip[6] = 0; ip[7] = 0
    ip[8] = 64
    ip[9] = ProtoTCP
    ip[10] = 0; ip[11] = 0
    for i := 0; i < 4; i++ { ip[12+i] = MyIP[i] }
    for i := 0; i < 4; i++ { ip[16+i] = c.dstIP[i] }
    // TCP header
    tcp := buf[34:54]
    tcp[0] = byte(c.srcPort >> 8); tcp[1] = byte(c.srcPort)
    tcp[2] = byte(c.dstPort >> 8); tcp[3] = byte(c.dstPort)
    // seq
    tcp[4] = byte(c.seq >> 24); tcp[5] = byte(c.seq >> 16); tcp[6] = byte(c.seq >> 8); tcp[7] = byte(c.seq)
    // ack
    tcp[8] = byte(c.ack >> 24); tcp[9] = byte(c.ack >> 16); tcp[10] = byte(c.ack >> 8); tcp[11] = byte(c.ack)
    // data offset 5, flags
    tcp[12] = (5 << 4)
    tcp[13] = flags
    // window
    tcp[14] = byte(c.window >> 8); tcp[15] = byte(c.window)
    // checksum
    tcp[16] = 0; tcp[17] = 0
    // urg
    tcp[18] = 0; tcp[19] = 0
    // payload
    plen := 0
    if payload != nil && len(payload) > 0 {
        // copy payload
        off := 54
        for i := 0; i < len(payload) && off+i < len(buf); i++ {
            buf[off+i] = payload[i]
            plen++
        }
        // store for potential retransmit
        ml := len(payload)
        if ml > len(c.lastData) { ml = len(c.lastData) }
        for i := 0; i < ml; i++ { c.lastData[i] = payload[i] }
    }
    total := 20 + 20 + plen
    ip[2] = byte(total >> 8); ip[3] = byte(total)
    // IPv4 checksum
    ics := checksum16(ip[:20])
    ip[10] = byte(ics >> 8); ip[11] = byte(ics)
    // TCP checksum over header+payload
    csum := tcpChecksum(buf[34:34+20+plen], MyIP, c.dstIP)
    tcp[16] = byte(csum >> 8); tcp[17] = byte(csum)
    frameLen := 14 + total
    if frameLen < 60 { frameLen = 60 }
    ok = e.SendFrame(buf[:frameLen])
    if ok {
        c.timer = 0
        c.lastLen = plen
        // mark base seq for outstanding
        if (flags & 0x02) != 0 || plen > 0 { c.unackedSeq = c.seq; c.outLen = 0 }
        // Advance seq for SYN or data we just sent (but keep outstanding tracked)
        if (flags & 0x02) != 0 { // SYN consumes 1 seq
            c.seq += 1
            c.outLen += 1
        }
        if plen > 0 {
            // data consumes len seq; but keep lastLen to await ACK
            // c.seq will be updated to end of data for next send attempt
            c.seq += uint32(plen)
            c.outLen += plen
        }
    }
    return ok
}

// Queue payload for sending (splits into small chunks)
func tcpQueue(c *TCPConn, data []byte) {
    i := 0
    for i < len(data) {
        if (c.qTail+1)%len(c.qLen) == c.qHead { break } // queue full
        // chunk size limited by buffer
        chunk := len(data) - i
        if chunk > len(c.qBuf[c.qTail]) { chunk = len(c.qBuf[c.qTail]) }
        // copy
        for k := 0; k < chunk; k++ { c.qBuf[c.qTail][k] = data[i+k] }
        c.qLen[c.qTail] = chunk
        c.qTail = (c.qTail + 1) % len(c.qLen)
        i += chunk
    }
}

// Attempt to send next queued chunk respecting cwnd and peer window; only 1 outstanding at a time
func tcpTrySend(e *nic.E1000, c *TCPConn) {
    if c.state != TCP_ESTABLISHED { return }
    if c.outLen > 0 { return } // wait for ACK
    if c.qHead == c.qTail { return }
    // window in bytes available
    wnd := int(c.window)
    if wnd <= 0 { return }
    // congestion window
    cwin := int(c.cwnd)
    sendCap := wnd
    if cwin < sendCap { sendCap = cwin }
    // choose payload size as min(mss, sendCap, queued chunk)
    n := c.qLen[c.qHead]
    if int(c.mss) < n { n = int(c.mss) }
    if n > sendCap { n = sendCap }
    if n <= 0 { return }
    payload := c.qBuf[c.qHead][:n]
    if tcpSendSegment(e, c, 0x18, payload) { // PSH+ACK
        // consume from queue head only after ACK; keep for retransmit
        c.lastLen = n
        // copy into lastData for retransmit
        for i := 0; i < n && i < len(c.lastData); i++ { c.lastData[i] = payload[i] }
        c.timer = 0
    }
}

// TCPConnect sends SYN and creates connection state
func TCPConnect(e *nic.E1000, dstIP [4]byte, dstPort uint16) *TCPConn {
    c := newTCPConn(dstIP, dstPort)
    if c == nil { return nil }
    c.state = TCP_SYN_SENT
    // Send SYN
    if tcpSendSegment(e, c, 0x02, nil) {
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        vga.Print("TCP SYN sent to "); printIP(dstIP); vga.Print(":"); printDec(int(dstPort)); vga.Println("")
        return c
    }
    c.state = TCP_CLOSED
    return nil
}

// Active close: send FIN then proceed through FIN_WAIT states
func TCPClose(e *nic.E1000, c *TCPConn) {
    if c == nil || !c.used { return }
    if c.state == TCP_ESTABLISHED {
        // require no outstanding data
        if c.outLen == 0 {
            tcpSendSegment(e, c, 0x11, nil) // FIN+ACK
            c.state = TCP_FIN_WAIT1
            c.timer = 0
        }
    }
}

func dispatchUDP(port uint16, srcIP [4]byte, srcPort uint16, data []byte) bool {
    for i := 0; i < udpCount; i++ {
        if udpPorts[i] == port && udpCbs[i] != nil {
            udpCbs[i](srcIP, srcPort, data)
            return true
        }
    }
    return false
}

// Helper listener that logs payload
func ListenUDPLogging(port uint16) bool {
    return ListenUDP(port, func(srcIP [4]byte, srcPort uint16, data []byte) {
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        vga.Print("UDP ")
        printDec(int(port))
        vga.Print(" <= ")
        printIP(srcIP)
        vga.Print(":")
        printDec(int(srcPort))
        vga.Print(" ")
        // print ascii payload
        for i := 0; i < len(data); i++ { vga.Print(string(rune(data[i]))) }
        vga.Println("")
    })
}

// Utility to ensure MAC is known
func ensureMAC(e *nic.E1000) {
    empty := true
    for i := 0; i < 6; i++ { if MyMAC[i] != 0 { empty = false; break } }
    if empty {
        mac := e.GetMAC()
        for i := 0; i < 6; i++ { MyMAC[i] = mac[i] }
    }
}

// UDP checksum including pseudo header
func udpChecksum(payload []byte, srcIP, dstIP [4]byte, srcPort, dstPort uint16) uint16 {
    var sum uint32
    // Pseudo header
    sum += uint32(srcIP[0])<<8 | uint32(srcIP[1])
    sum += uint32(srcIP[2])<<8 | uint32(srcIP[3])
    sum += uint32(dstIP[0])<<8 | uint32(dstIP[1])
    sum += uint32(dstIP[2])<<8 | uint32(dstIP[3])
    sum += uint32(ProtoUDP)
    length := uint16(8 + len(payload))
    sum += uint32(length)
    // UDP header
    sum += uint32(srcPort)
    sum += uint32(dstPort)
    sum += uint32(length)
    // payload
    for i := 0; i+1 < len(payload); i += 2 {
        sum += uint32(payload[i])<<8 | uint32(payload[i+1])
    }
    if len(payload)%2 == 1 { sum += uint32(payload[len(payload)-1]) << 8 }
    for (sum >> 16) != 0 { sum = (sum & 0xFFFF) + (sum >> 16) }
    return ^uint16(sum)
}

// TCP checksum including pseudo header
func tcpChecksum(segment []byte, srcIP, dstIP [4]byte) uint16 {
    var sum uint32
    // Pseudo header
    sum += uint32(srcIP[0])<<8 | uint32(srcIP[1])
    sum += uint32(srcIP[2])<<8 | uint32(srcIP[3])
    sum += uint32(dstIP[0])<<8 | uint32(dstIP[1])
    sum += uint32(dstIP[2])<<8 | uint32(dstIP[3])
    sum += uint32(ProtoTCP)
    sum += uint32(len(segment))
    for i := 0; i+1 < len(segment); i += 2 {
        sum += uint32(segment[i])<<8 | uint32(segment[i+1])
    }
    if len(segment)%2 == 1 { sum += uint32(segment[len(segment)-1]) << 8 }
    for (sum >> 16) != 0 { sum = (sum & 0xFFFF) + (sum >> 16) }
    return ^uint16(sum)
}

// SendUDP constructs and sends a UDP datagram.
func SendUDP(e *nic.E1000, dstIP [4]byte, dstPort uint16, srcPort uint16, data []byte) bool {
    ensureMAC(e)
    nh := nextHop(dstIP)
    mac, ok := LookupMAC(nh)
    if !ok {
        mac, ok = ResolveMACBlocking(e, nh, 8)
        if !ok {
            vga.SetColor(vga.ColorYellow, vga.ColorBlack)
            vga.Println("UDP: MAC unknown; ARP sent (no reply)")
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            return false
        }
    }
    var buf [256]byte
    // Ethernet
    for i := 0; i < 6; i++ { buf[i] = mac[i] }
    for i := 0; i < 6; i++ { buf[6+i] = MyMAC[i] }
    buf[12] = 0x08; buf[13] = 0x00
    // IPv4
    ip := buf[14:34]
    ip[0] = 0x45
    ip[1] = 0
    // total later
    ip[4] = 0x56; ip[5] = 0x78
    ip[6] = 0; ip[7] = 0
    ip[8] = 64
    ip[9] = ProtoUDP
    ip[10] = 0; ip[11] = 0
    for i := 0; i < 4; i++ { ip[12+i] = MyIP[i] }
    for i := 0; i < 4; i++ { ip[16+i] = dstIP[i] }
    // UDP
    udp := buf[34:]
    udp[0] = byte(srcPort >> 8); udp[1] = byte(srcPort & 0xFF)
    udp[2] = byte(dstPort >> 8); udp[3] = byte(dstPort & 0xFF)
    ulen := 8 + len(data)
    udp[4] = byte(ulen >> 8); udp[5] = byte(ulen & 0xFF)
    udp[6] = 0; udp[7] = 0
    // payload
    for i := 0; i < len(data); i++ { udp[8+i] = data[i] }
    // IP total
    total := 20 + ulen
    ip[2] = byte(total >> 8); ip[3] = byte(total & 0xFF)
    // IPv4 checksum
    ip[10] = 0; ip[11] = 0
    csum := checksum16(ip[:20])
    ip[10] = byte(csum >> 8); ip[11] = byte(csum & 0xFF)
    // UDP checksum
    udp[6] = 0; udp[7] = 0
    ucs := udpChecksum(udp[8:8+len(data)], MyIP, dstIP, srcPort, dstPort)
    udp[6] = byte(ucs >> 8); udp[7] = byte(ucs & 0xFF)
    // Frame length
    frameLen := 14 + total
    if frameLen < 60 { frameLen = 60 }
    ok = e.SendFrame(buf[:frameLen])
    if ok {
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        vga.Print("UDP sent to ")
        printIP(dstIP)
        vga.Print(":")
        printDec(int(dstPort))
        vga.Println("")
    }
    return ok
}

// SendUDPBroadcast crafts a UDP frame to L2 broadcast (for DHCP, etc.).
func SendUDPBroadcast(e *nic.E1000, dstPort uint16, srcPort uint16, data []byte, srcIP [4]byte) bool {
    ensureMAC(e)
    var buf [512]byte
    // Ethernet: broadcast
    for i := 0; i < 6; i++ { buf[i] = 0xFF }
    for i := 0; i < 6; i++ { buf[6+i] = MyMAC[i] }
    buf[12] = 0x08; buf[13] = 0x00
    // IPv4
    ip := buf[14:34]
    ip[0] = 0x45
    ip[1] = 0
    ip[4] = 0xAB; ip[5] = 0xCD
    ip[6] = 0; ip[7] = 0
    ip[8] = 64
    ip[9] = ProtoUDP
    ip[10] = 0; ip[11] = 0
    for i := 0; i < 4; i++ { ip[12+i] = srcIP[i] }
    ip[16] = 255; ip[17] = 255; ip[18] = 255; ip[19] = 255
    // UDP
    udp := buf[34:]
    udp[0] = byte(srcPort >> 8); udp[1] = byte(srcPort)
    udp[2] = byte(dstPort >> 8); udp[3] = byte(dstPort)
    ulen := 8 + len(data)
    udp[4] = byte(ulen >> 8); udp[5] = byte(ulen)
    udp[6] = 0; udp[7] = 0
    for i := 0; i < len(data); i++ { udp[8+i] = data[i] }
    total := 20 + ulen
    ip[2] = byte(total >> 8); ip[3] = byte(total)
    // IPv4 checksum
    ip[10] = 0; ip[11] = 0
    ics := checksum16(ip[:20])
    ip[10] = byte(ics >> 8); ip[11] = byte(ics)
    // UDP checksum
    udp[6] = 0; udp[7] = 0
    ucs := udpChecksum(udp[8:8+len(data)], srcIP, [4]byte{255,255,255,255}, srcPort, dstPort)
    udp[6] = byte(ucs >> 8); udp[7] = byte(ucs)
    frameLen := 14 + total
    if frameLen < 60 { frameLen = 60 }
    return e.SendFrame(buf[:frameLen])
}

// Encode DNS name into labels.
func encodeDNSName(name string, out *[128]byte) int {
    n := 0
    start := 0
    for i := 0; i <= len(name); i++ {
        if i == len(name) || name[i] == '.' {
            ln := i - start
            if ln <= 0 || n+1+ln >= len(out) { return n }
            out[n] = byte(ln); n++
            for j := 0; j < ln; j++ { out[n] = name[start+j]; n++ }
            start = i + 1
        }
    }
    if n < len(out) { out[n] = 0; n++ }
    return n
}

// SendDNSQuery crafts and sends a minimal DNS A query over UDP.
func SendDNSQuery(e *nic.E1000, server [4]byte, name string) bool {
    var msg [128]byte
    // Header
    msg[0] = 0x12; msg[1] = 0x34 // ID
    msg[2] = 0x01; msg[3] = 0x00 // flags: RD
    msg[4] = 0x00; msg[5] = 0x01 // QDCOUNT 1
    msg[6] = 0; msg[7] = 0       // ANCOUNT 0
    msg[8] = 0; msg[9] = 0       // NSCOUNT
    msg[10] = 0; msg[11] = 0     // ARCOUNT
    off := 12
    var qname [128]byte
    qn := encodeDNSName(name, &qname)
    for i := 0; i < qn; i++ { msg[off+i] = qname[i] }
    off += qn
    // QTYPE=A, QCLASS=IN
    msg[off+0] = 0; msg[off+1] = 1
    msg[off+2] = 0; msg[off+3] = 1
    off += 4
    return SendUDP(e, server, 53, 40000, msg[:off])
}

// Minimal DNS response parsing: print A records.
func handleDNSResponse(p []byte, srcIP [4]byte) {
    if len(p) < 12 { return }
    qd := int(p[4])<<8 | int(p[5])
    an := int(p[6])<<8 | int(p[7])
    off := 12
    // skip questions
    for qi := 0; qi < qd; qi++ {
        // read labels until 0
        for off < len(p) {
            ln := int(p[off]); off++
            if ln == 0 { break }
            off += ln
        }
        off += 4 // type,class
    }
    // answers
    for ai := 0; ai < an && off+10 <= len(p); ai++ {
        // NAME (compression or labels)
        if off+2 > len(p) { break }
        // if compressed name (0xC0xx), skip 2; else skip labels
        if (p[off] & 0xC0) == 0xC0 { off += 2 } else {
            for off < len(p) {
                ln := int(p[off]); off++
                if ln == 0 { break }
                off += ln
            }
        }
        if off+10 > len(p) { break }
        typ := int(p[off])<<8 | int(p[off+1])
        cls := int(p[off+2])<<8 | int(p[off+3])
        _ = cls
        off += 4
        off += 4 // TTL
        rdlen := int(p[off])<<8 | int(p[off+1])
        off += 2
        if typ == 1 && rdlen == 4 && off+4 <= len(p) { // A
            ip := [4]byte{p[off], p[off+1], p[off+2], p[off+3]}
            vga.SetColor(vga.ColorLightGreen, vga.ColorBlack)
            vga.Print("DNS A ")
            printIP(ip)
            vga.Print(" from ")
            printIP(srcIP)
            vga.Println("")
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        }
        off += rdlen
    }
}

// SendTCPSyn crafts and sends a minimal TCP SYN segment.
func SendTCPSyn(e *nic.E1000, dstIP [4]byte, dstPort uint16) bool {
    ensureMAC(e)
    nh := nextHop(dstIP)
    mac, ok := LookupMAC(nh)
    if !ok {
        mac, ok = ResolveMACBlocking(e, nh, 8)
        if !ok {
            vga.SetColor(vga.ColorYellow, vga.ColorBlack)
            vga.Println("TCP: MAC unknown; ARP sent (no reply)")
            vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
            return false
        }
    }
    var buf [128]byte
    // Ethernet
    for i := 0; i < 6; i++ { buf[i] = mac[i] }
    for i := 0; i < 6; i++ { buf[6+i] = MyMAC[i] }
    buf[12] = 0x08; buf[13] = 0x00
    // IPv4
    ip := buf[14:34]
    ip[0] = 0x45
    ip[1] = 0
    ip[4] = 0x9A; ip[5] = 0xBC
    ip[6] = 0; ip[7] = 0
    ip[8] = 64
    ip[9] = ProtoTCP
    ip[10] = 0; ip[11] = 0
    for i := 0; i < 4; i++ { ip[12+i] = MyIP[i] }
    for i := 0; i < 4; i++ { ip[16+i] = dstIP[i] }
    // TCP header (20 bytes)
    tcp := buf[34:54]
    sport := uint16(40001)
    tcp[0] = byte(sport >> 8); tcp[1] = byte(sport)
    tcp[2] = byte(dstPort >> 8); tcp[3] = byte(dstPort)
    // seq
    tcp[4] = 0; tcp[5] = 0; tcp[6] = 0; tcp[7] = 0
    // ack
    tcp[8] = 0; tcp[9] = 0; tcp[10] = 0; tcp[11] = 0
    // data offset 5, flags SYN
    tcp[12] = (5 << 4)
    tcp[13] = 0x02
    // window
    tcp[14] = 0x04; tcp[15] = 0x00
    // checksum
    tcp[16] = 0; tcp[17] = 0
    // urg ptr
    tcp[18] = 0; tcp[19] = 0
    // total
    total := 20 + 20
    ip[2] = byte(total >> 8); ip[3] = byte(total)
    // IPv4 checksum
    ip[10] = 0; ip[11] = 0
    ics := checksum16(ip[:20])
    ip[10] = byte(ics >> 8); ip[11] = byte(ics)
    // TCP checksum over header only
    c := tcpChecksum(tcp[:20], MyIP, dstIP)
    tcp[16] = byte(c >> 8); tcp[17] = byte(c)
    // Frame length
    frameLen := 14 + total
    if frameLen < 60 { frameLen = 60 }
    ok = e.SendFrame(buf[:frameLen])
    if ok {
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        vga.Print("TCP SYN sent to ")
        printIP(dstIP)
        vga.Print(":")
        printDec(int(dstPort))
        vga.Println("")
    }
    return ok
}

// ResolveMACBlocking attempts ARP resolution by sending a request and polling RX.
func ResolveMACBlocking(e *nic.E1000, ip [4]byte, attempts int) ([6]byte, bool) {
    if mac, ok := LookupMAC(ip); ok { return mac, true }
    // trigger ARP
    SendARPRequest(e, ip)
    for i := 0; i < attempts; i++ {
        // Poll RX to process incoming ARP replies; this calls HandleEther via RxHandler.
        e.DumpRx(8)
        if mac, ok := LookupMAC(ip); ok { return mac, true }
    }
    return [6]byte{}, false
}
// SendARPRequest crafts and sends an ARP request for target IP.
func SendARPRequest(e *nic.E1000, target [4]byte) bool {
    ensureMAC(e)
    var frame [60]byte
    // Ethernet header
    for i := 0; i < 6; i++ { frame[i] = 0xFF }              // dst: broadcast
    for i := 0; i < 6; i++ { frame[6+i] = MyMAC[i] }        // src
    frame[12] = 0x08; frame[13] = 0x06                      // ARP
    // ARP payload (28 bytes)
    off := 14
    frame[off+0] = 0x00; frame[off+1] = 0x01               // HTYPE Ethernet
    frame[off+2] = 0x08; frame[off+3] = 0x00               // PTYPE IPv4
    frame[off+4] = 0x06                                    // HLEN
    frame[off+5] = 0x04                                    // PLEN
    frame[off+6] = 0x00; frame[off+7] = 0x01               // OPER request
    // SHA
    for i := 0; i < 6; i++ { frame[off+8+i] = MyMAC[i] }
    // SPA
    for i := 0; i < 4; i++ { frame[off+14+i] = MyIP[i] }
    // THA
    for i := 0; i < 6; i++ { frame[off+18+i] = 0x00 }
    // TPA
    for i := 0; i < 4; i++ { frame[off+24+i] = target[i] }

    ok := e.SendFrame(frame[:])
    if ok {
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        vga.Print("ARP request sent to ")
        printIP(target)
        vga.Println("")
    }
    return ok
}

// Checksum helpers
func checksum16(b []byte) uint16 {
    var sum uint32
    for i := 0; i+1 < len(b); i += 2 {
        sum += uint32(b[i])<<8 | uint32(b[i+1])
    }
    if len(b)%2 == 1 { sum += uint32(b[len(b)-1]) << 8 }
    for (sum >> 16) != 0 { sum = (sum & 0xFFFF) + (sum >> 16) }
    return ^uint16(sum)
}

// SendICMPEcho crafts and sends a minimal ICMP echo request.
func SendICMPEcho(e *nic.E1000, dstIP [4]byte) bool {
    ensureMAC(e)
    nh := nextHop(dstIP)
    mac, ok := LookupMAC(nh)
    if !ok {
        // Trigger ARP discovery for next-hop
        SendARPRequest(e, nh)
        vga.SetColor(vga.ColorYellow, vga.ColorBlack)
        vga.Print("Unknown MAC; ARP sent, retry ping after reply")
        vga.Println("")
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        return false
    }
    // Build Ethernet + IPv4 + ICMP
    var buf [128]byte
    // Ethernet
    for i := 0; i < 6; i++ { buf[i] = mac[i] }
    for i := 0; i < 6; i++ { buf[6+i] = MyMAC[i] }
    buf[12] = 0x08; buf[13] = 0x00
    // IPv4 header
    ip := buf[14:34]
    ip[0] = 0x45           // v4, IHL=5
    ip[1] = 0              // TOS
    // total length filled later
    ip[4] = 0x12; ip[5] = 0x34 // ID
    ip[6] = 0; ip[7] = 0       // flags/frag
    ip[8] = 64                 // TTL
    ip[9] = 1                  // protocol ICMP
    ip[10] = 0; ip[11] = 0     // checksum later
    for i := 0; i < 4; i++ { ip[12+i] = MyIP[i] }
    for i := 0; i < 4; i++ { ip[16+i] = dstIP[i] }
    // ICMP echo
    icmp := buf[34:]
    icmp[0] = 8; icmp[1] = 0   // type echo, code 0
    icmp[2] = 0; icmp[3] = 0   // checksum later
    icmp[4] = 0xBE; icmp[5] = 0xEF // identifier
    icmp[6] = 0; icmp[7] = 1       // seq
    payloadLen := 32
    for i := 0; i < payloadLen; i++ { icmp[8+i] = byte('A' + (i%26)) }
    icmpLen := 8 + payloadLen
    // fill total length
    total := 20 + icmpLen
    ip[2] = byte(total >> 8); ip[3] = byte(total & 0xFF)
    // IPv4 checksum
    ip[10] = 0; ip[11] = 0
    csum := checksum16(ip[:20])
    ip[10] = byte(csum >> 8); ip[11] = byte(csum & 0xFF)
    // ICMP checksum
    icmp[2] = 0; icmp[3] = 0
    ics := checksum16(icmp[:icmpLen])
    icmp[2] = byte(ics >> 8); icmp[3] = byte(ics & 0xFF)
    // Total Ethernet frame length
    frameLen := 14 + total
    if frameLen < 60 { frameLen = 60 }
    ok = e.SendFrame(buf[:frameLen])
    if ok {
        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
        vga.Print("ICMP echo sent to ")
        printIP(dstIP)
        vga.Println("")
    }
    return ok
}

// DumpARP prints current ARP cache entries.
func DumpARP() {
    vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
    vga.Println("ARP cache:")
    for i := 0; i < len(arpCache); i++ {
        if arpCache[i].valid {
            printIP(arpCache[i].ip)
            vga.Print(" -> ")
            for j := 0; j < 6; j++ {
                if j != 0 { vga.Print(":") }
                printHexByte(arpCache[i].mac[j])
            }
            vga.Println("")
        }
    }
}
// Init registers RX handler to NIC package.
func Init() {
    nic.RxHandler = HandleEther
}

// Choose next hop based on netmask/gateway
func nextHop(dst [4]byte) [4]byte {
    // if same subnet
    same := true
    for i := 0; i < 4; i++ {
        if (dst[i] & Netmask[i]) != (MyIP[i] & Netmask[i]) { same = false; break }
    }
    if same { return dst }
    return Gateway
}

// Periodic tick: expire ARP entries and poll NIC RX
var arpTTL [8]uint32

func Tick() {
    // expire simplistic TTL
    for i := 0; i < len(arpCache); i++ {
        if arpCache[i].valid {
            if arpTTL[i] > 0 { arpTTL[i]-- } else { arpCache[i].valid = false }
        }
    }
    // poll RX (lightweight)
    if e, ok := nic.FindE1000(); ok {
        e.DumpRx(8)
        // TCP retransmission timers
        for i := 0; i < tcpCount; i++ {
            c := &tcpConns[i]
            c.timer++
            if c.state == TCP_SYN_SENT {
                if c.timer > c.rto {
                    if c.retries < 3 {
                        // resend SYN using unackedSeq
                        old := c.seq
                        c.seq = c.unackedSeq
                        tcpSendSegment(e, c, 0x02, nil)
                        c.seq = old
                        c.retries++
                        c.timer = 0
                        // exponential backoff
                        if c.backoff < 6 { c.backoff++ }
                        c.rto = c.rto << 1
                        vga.SetColor(vga.ColorYellow, vga.ColorBlack)
                        vga.Println("TCP: retransmit SYN")
                        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                    } else {
                        c.state = TCP_CLOSED
                        vga.SetColor(vga.ColorLightRed, vga.ColorBlack)
                        vga.Println("TCP: SYN timeout, giving up")
                        vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                    }
                }
            } else if c.state == TCP_ESTABLISHED {
                if c.lastLen > 0 && c.timer > c.rto {
                    // resend unacked data
                    old := c.seq
                    c.seq = c.unackedSeq
                    payload := c.lastData[:c.lastLen]
                    tcpSendSegment(e, c, 0x18, payload)
                    c.seq = old
                    c.timer = 0
                    if c.backoff < 6 { c.backoff++ }
                    c.rto = c.rto << 1
                    // congestion control on timeout
                    if c.cwnd/2 > c.mss { c.ssthresh = c.cwnd / 2 } else { c.ssthresh = c.mss }
                    c.cwnd = c.mss
                    vga.SetColor(vga.ColorYellow, vga.ColorBlack)
                    vga.Println("TCP: retransmit data")
                    vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
                }
            } else if c.state == TCP_FIN_WAIT1 || c.state == TCP_LAST_ACK {
                // simple FIN retransmit on timeout
                if c.timer > c.rto {
                    old := c.seq
                    c.seq = c.unackedSeq
                    tcpSendSegment(e, c, 0x11, nil)
                    c.seq = old
                    c.timer = 0
                    if c.backoff < 6 { c.backoff++ }
                    c.rto = c.rto << 1
                }
            } else if c.state == TCP_TIME_WAIT {
                // after fixed duration, free connection
                if c.timer > 500 {
                    c.state = TCP_CLOSED
                    c.used = false
                }
            }
        }
    }
}

// DHCP minimal: discover and offer handling
func StartDHCP(e *nic.E1000) {
    // Register UDP listener for client port 68 to accept offers/acks
    ListenUDP(68, func(srcIP [4]byte, srcPort uint16, data []byte) {
        handleDHCP(data)
    })
    // Build minimal discover
    var msg [300]byte
    off := 0
    msg[off+0] = 0x01 // bootrequest
    msg[off+1] = 0x01 // htype ethernet
    msg[off+2] = 0x06 // hlen
    msg[off+3] = 0x00 // hops
    // xid
    msg[off+4] = 0x12; msg[off+5] = 0x34; msg[off+6] = 0x56; msg[off+7] = 0x78
    // secs/flags
    msg[off+8] = 0; msg[off+9] = 0
    msg[off+10] = 0x80; msg[off+11] = 0x00 // broadcast flag
    // ciaddr, yiaddr, siaddr, giaddr = 0
    for i := 12; i < 28; i++ { msg[off+i] = 0 }
    // chaddr
    ensureMAC(e)
    for i := 0; i < 6; i++ { msg[off+28+i] = MyMAC[i] }
    // sname/file zeros
    for i := 34; i < 236; i++ { msg[off+i] = 0 }
    // magic cookie
    msg[236] = 99; msg[237] = 130; msg[238] = 83; msg[239] = 99
    o := 240
    // DHCP Message Type: Discover
    msg[o+0] = 53; msg[o+1] = 1; msg[o+2] = 1; o += 3
    // Parameter Request List: subnet mask (1), router (3), DNS (6)
    msg[o+0] = 55; msg[o+1] = 3; msg[o+2] = 1; msg[o+3] = 3; msg[o+4] = 6; o += 5
    // End
    msg[o] = 255; o++
    // Send as broadcast src IP 0.0.0.0 src port 68 dst 67
    SendUDPBroadcast(e, 67, 68, msg[:o], [4]byte{0,0,0,0})
}

// Handle DHCP offer on UDP dest port 68
func handleDHCP(payload []byte) {
    if len(payload) < 240 { return }
    yi := [4]byte{payload[16], payload[17], payload[18], payload[19]}
    // options
    off := 240
    var mask, router, dns [4]byte
    for off+2 <= len(payload) {
        code := payload[off]; off++
        if code == 0 { continue }
        if code == 255 { break }
        ln := int(payload[off]); off++
        if off+ln > len(payload) { break }
        switch code {
        case 1: if ln >= 4 { mask = [4]byte{payload[off],payload[off+1],payload[off+2],payload[off+3]} }
        case 3: if ln >= 4 { router = [4]byte{payload[off],payload[off+1],payload[off+2],payload[off+3]} }
        case 6: if ln >= 4 { dns = [4]byte{payload[off],payload[off+1],payload[off+2],payload[off+3]} }
        }
        off += ln
    }
    // apply config
    MyIP = yi
    if mask != ([4]byte{}) { Netmask = mask }
    if router != ([4]byte{}) { Gateway = router }
    if dns != ([4]byte{}) { DNS = dns }
    vga.SetColor(vga.ColorLightGreen, vga.ColorBlack)
    vga.Print("DHCP: IP "); printIP(MyIP)
    vga.Print(" mask "); printIP(Netmask)
    vga.Print(" gw "); printIP(Gateway)
    vga.Print(" dns "); printIP(DNS)
    vga.Println("")
    vga.SetColor(vga.ColorLightGray, vga.ColorBlack)
    // gratuitous ARP
    if e, ok := nic.FindE1000(); ok { SendGratuitousARP(e) }
}

func SendGratuitousARP(e *nic.E1000) bool {
    ensureMAC(e)
    var frame [60]byte
    // Ethernet broadcast
    for i := 0; i < 6; i++ { frame[i] = 0xFF }
    for i := 0; i < 6; i++ { frame[6+i] = MyMAC[i] }
    frame[12] = 0x08; frame[13] = 0x06
    off := 14
    frame[off+0] = 0x00; frame[off+1] = 0x01
    frame[off+2] = 0x08; frame[off+3] = 0x00
    frame[off+4] = 0x06
    frame[off+5] = 0x04
    frame[off+6] = 0x00; frame[off+7] = 0x01 // request
    for i := 0; i < 6; i++ { frame[off+8+i] = MyMAC[i] }
    for i := 0; i < 4; i++ { frame[off+14+i] = MyIP[i] }
    // THA = 0, TPA = MyIP (announce)
    for i := 0; i < 6; i++ { frame[off+18+i] = 0 }
    for i := 0; i < 4; i++ { frame[off+24+i] = MyIP[i] }
    return e.SendFrame(frame[:])
}